//! http 签名
use base64;
use hmac::{Hmac, Mac};
use http::HeaderMap;
use sha2::Sha256;
use std::collections::BTreeMap;
type HmacSha256 = Hmac<Sha256>;
pub fn sign(
    app_secret: &str,
    method: &str,
    headers: &HeaderMap,
    path_and_query: &str,
    queries: &BTreeMap<String, String>,
    form: &BTreeMap<String, String>,
) -> Result<String, String> {
    let string = build_string_to_sign(method, headers, path_and_query, queries, form);
    //let string = "";
    HmacSha256::new_varkey(app_secret.as_bytes())
        .map_err(|e| e.to_string())
        .and_then(|mut mac| {
            mac.input(string.as_bytes());
            Ok(base64::encode(mac.result().code().as_slice()))
        })
}

pub fn content_md5(body: &str) -> String {
    base64::encode(&md5::compute(body.as_bytes()).0[..])
}
pub fn content_md5_raw(body: &[u8]) -> String {
    base64::encode(&md5::compute(body).0[..])
}

fn build_string_to_sign(
    method: &str,
    headers: &HeaderMap,
    path_and_query: &str,
    queries: &BTreeMap<String, String>,
    form: &BTreeMap<String, String>,
) -> String {
    let sign_header: Vec<String> = headers
        .get_all("")
        .iter()
        .filter_map(|s| s.to_str().ok().map(|s| s.to_owned()))
        .collect();
    let strss = format!(
        "{}\n{}\n{}\n{}\n{}\n{}{}",
        method,
        headers
            .get("Accept")
            .and_then(|s| s.to_str().ok())
            .unwrap_or_default(),
        headers
            .get("Content-MD5")
            .and_then(|s| s.to_str().ok())
            .unwrap_or_default(),
        headers
            .get("Content-Type")
            .and_then(|s| s.to_str().ok())
            .unwrap_or_default(),
        headers
            .get("Date")
            .and_then(|s| s.to_str().ok())
            .unwrap_or_default(),
        build_headers(headers, sign_header),
        build_resource(path_and_query, queries, form)
    );
    log::info!("build_string_to_sign: {}", strss);
    strss
}

/// 将path、queryParam、formParam合成一个字符串
fn build_resource(
    path_and_query: &str,
    queries: &BTreeMap<String, String>,
    form: &BTreeMap<String, String>,
) -> String {
    let f = |b: &BTreeMap<String, String>| {
        let mut string = String::new();
        b.iter().for_each(|(k, v)| {
            string += &*format!(
                "{}{}&",
                k,
                if v == "" {
                    "".to_string()
                } else {
                    format!("={}", v)
                },
            );
        });
        string
    };

    let mut string = f(queries) + &*f(form);
    string.pop();

    let result = if string.is_empty() {
        path_and_query.to_owned()
    } else {
        format!("{}?{}", path_and_query, string)
    };
    log::info!("build_resource: {}", result);
    result
}

/// 将headers合成一个字符串
///
/// 需要注意的是，HTTP头需要按照字母排序加入签名字符串
///
///  同时所有加入签名的头的列表，需要用逗号分隔形成一个字符串，加入一个新HTTP头@"X-Ca-Signature-Headers"
fn build_headers(headers: &HeaderMap, sign_header: Vec<String>) -> String {
    let mut her = HeaderMap::new();
    headers.iter().for_each(|(k, v)| {
        her.insert(k.clone(), v.clone());
    });
    her.remove("X-NPW-Sign");
    her.remove("Accept");
    her.remove("Content-Type");
    her.remove("Date");
    let mut params = BTreeMap::new();
    let mut h_iter = sign_header.iter();
    her.iter()
        .map(|(k, v)| (k.as_str().to_owned(), v))
        .for_each(|(k, v)| {
            if !k.starts_with("X-NPW-") && h_iter.find(|x| k.eq(*x)).is_some() {
                params.insert(k.to_string(), v.to_str().map(|s| s.to_string()).ok());
            }
        });
    let mut string = String::new();
    params.iter().for_each(|(k, v)| {
        string += &*format!("{}:{}\n", k.as_str(), v.clone().unwrap_or_default());
    });
    string.pop();
    log::info!("build_headers: {}", string);
    string
}
